Guava的RateLimiter提供的令牌桶算法可以用于平滑突发限流(SmoothBursty
)和平滑预热限流(SmoothWarmingUp
)实现
SmoothBursty
平滑突发限流(SmoothBursty
)顾名思义,就是允许突发的流量进入,后面再慢慢地平稳限流。
SmoothBursty
的demo:
1 | RateLimiter rateLimiter = RateLimiter.create(1.0); |
运行结果:
可以看出,SmoothBursty
允许获取的令牌数量可以超过最大令牌数的限制,但是之后获取令牌的请求需要等待一定的时间来补充之前透支的令牌。
SmoothBursty中几个属性的含义:
1 | /** |
SmoothBursty的创建过程
因为SmoothBursty
的可见性是默认的包可见,因此它只能通过RateLimiter
提供的静态方法来创建。创建方法如下:
1 | public static RateLimiter create(double permitsPerSecond) { |
可以看到,SmoothBursty
创建时传入一个SleepingStopwatch
(用于计时和睡眠),和一个maxBurstSeconds
参数。
maxBurstSeconds
表示令牌统计的时间范围,默认为1。即一秒钟内发放permitsPerSecond
个令牌。该参数的作用在于更为灵活地控制流量,比如控制3秒内发放10个令牌。因为SmoothBursty
无法自由创建,因此这个参数目前无法修改。
接着调用setRate
方法设置令牌产生的速率:
1 | public final void setRate(double permitsPerSecond) { |
acquire方法
acquire
方法主要用于获取permits个令牌,并计算需要等待多长时间,进而挂起等待,并将该值返回。
1 | public double acquire() { |
acquire
方法调用reserve
方法获取需要等待的时间长度,然后等待给定的时间,最后返回等待的时间。
reserve
方法实际调用的是reserveAndGetWaitLength
方法,reserveAndGetWaitLength
方法调用reserveEarliestAvailable
获取下一次请求可以获取令牌的起始时间
,即必须等待到这个时间才能获取令牌。将该时间与当前时间做差值即是需要等待的时间长度。
reserveEarliestAvailable方法
reserveEarliestAvailable
方法用于获取requiredPermits
个令牌,并返回需要等待到的时间点。
1 | /** |
需要注意的是,该函数返回的是更新前的(上次请求计算的)nextFreeTicketMicros
,而不是本次更新的nextFreeTicketMicros
。通俗来讲,本次请求需要为上次请求的预消费行为埋单,这也是RateLimiter可以预消费(处理突发)的原理所在。若需要禁止预消费,则修改此处返回更新后的nextFreeTicketMicros
值。
resync方法
resync
方法用于更新当前存储的令牌数(storedPermits
)以及下一次请求可以获取令牌的起始时间(nextFreeTicketMicros
)。
1 | void resync(long nowMicros) { |
根据令牌桶算法,桶中的令牌持续生成存放的,有请求时需要先从桶中拿到令牌才能开始执行,谁来持续生成令牌存放呢?
一种解法是,开启一个定时任务,由定时任务持续生成令牌。这样的问题在于会极大的消耗系统资源,如某接口需要分别对每个用户做访问频率限制,假设系统中存在6W用户,则至多需要开启6W个定时任务来维持每个桶中的令牌数,这样的开销是巨大的。
另一种解法则是延迟计算,如上resync
函数。该函数会在每次获取令牌之前调用,其实现思路为,若当前时间晚于nextFreeTicketMicros
,则计算该段时间内可以生成多少令牌,将生成的令牌桶中并更新数据。这样一来,只需要在获取令牌时计算一次即可。
tryAcquire方法
tryAcquire
函数调用canAcquire
方法尝试在timeout
时间内获取令牌,如果可以则挂起等待相应的时间,并返回true
,否则立即返回false
。
1 | public boolean tryAcquire() { |
canAcquire方法
canAcquire
方法用于判断timeout
时间内是否可以获取令牌。
1 | private boolean canAcquire(long nowMicros, long timeoutMicros) { |
SmoothWarmingUp
平滑突发限流有可能瞬间带来很大的流量,如果系统扛不住的话,很容易造成系统挂掉。这时候,平滑预热限流便可以解决这个问题。
SmoothWarmingUp
的demo:
1 | RateLimiter rateLimiter = RateLimiter.create(5, 1, TimeUnit.SECONDS); |
运行结果如下:
可以看出平滑预热限流的耗时是慢慢趋近平均值的。
SmoothWarmingUp的创建过程
SmoothWarmingUp
只能通过RateLimiter
提供的静态方法来创建。创建方法如下:
1 | public static RateLimiter create(double permitsPerSecond, long warmupPeriod, TimeUnit unit) { |
可以看到,相比于SmoothBursty
的创建过程,创建SmoothWarmingUp
除了需要permitsPerSecond
参数外,还需要另外的3个参数:
- warmupPeriod:从冷启动速率过滤到平均速率所需要的时间间隔,即预热时间
- unit:warmupPeriod的时间单位
- coldFactor:冷启动系数
根据下面这张图,我们来解释SmoothRateLimiter
中各个参数的作用以及它的初始化过程
1 | * ^ throttling |
简单来说,上图展示了一种机制:当前存储的令牌数(storedPermits
)越多,生成令牌的间隔时间就越长。当存储的令牌数到达最大值(maxPermits
)生成令牌的间隔时间也到达最大值(cold interval
)。cold interval
同时受stable interval
和coldFactor
的影响,是两者的乘积,coldFactor
默认为3.0,即cold interval
是stable interval
的3倍。thresholdPermits
是一个拐点,当令牌数小于thresholdPermits
时生成令牌的间隔时间稳定在stable interval
;当令牌数大于thresholdPermits
时,生成令牌的间隔时间以一个固定的速率发生变化。thresholdPermits
等于预热时间内产生令牌数量的一半。
1 | static final class SmoothWarmingUp extends SmoothRateLimiter { |
storedPermitsToWaitTime方法
与SmoothBursty
中的storedPermitsToWaitTime
方法(直接返回0
)不同,SmoothWarmingUp
中的storedPermitsToWaitTime
方法需要根据令牌数与thresholdPermits的关系来计算等待的时间。
1 | long storedPermitsToWaitTime(double storedPermits, double permitsToTake) { |
coolDownIntervalMicros方法
与SmoothBursty
中的coolDownIntervalMicros
方法(直接返回stableIntervalMicros
)不同,与SmoothWarmingUp
中的coolDownIntervalMicros
方法如下:
1 | double coolDownIntervalMicros() { |
https://segmentfault.com/a/1190000012875897
http://www.voidcn.com/article/p-xrbsgqit-ct.html